package framework.states;

import framework.BasicActionGetter;
import framework.BasicActionGetterInstance;
import framework.BasicBuffer;
import framework.BasicFighter;
import framework.Controls;
import framework.DebugDrawer;
import framework.FightTimer;
import framework.FontManager;
import framework.HealthBar;
import framework.Stage;
import framework.attack.BasicAttack;
import framework.collision.FighterCollisionManager;
import framework.collision.SimpleAttackCollisionManager;
import framework.ia.Brain;

import java.util.Arrays;

import org.newdawn.slick.AngelCodeFont;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.state.BasicGameState;
import org.newdawn.slick.state.StateBasedGame;
import org.newdawn.slick.state.transition.FadeInTransition;
import org.newdawn.slick.state.transition.FadeOutTransition;

/**
 * @author blaze
 */
public class Fight extends BasicGameState {

	private static final int DEFAULT_FIGHT_DURATION = 60;

	public static final int ID = 1;

	private static final long TIME_START = 1500;

	protected BasicFighter fighter1;

	protected BasicFighter fighter2;

	protected HealthBar health1;

	protected HealthBar health2;

	protected Stage stage;

	protected int fightDuration;

	protected long initTime;

	protected int timer;
	
	protected BasicBuffer<Controls> buffer;

	protected BasicFighter[] ranking;

	protected FighterCollisionManager collisions;

	protected SimpleAttackCollisionManager attackCollisions;

	private DebugDrawer debug;

	private boolean isDebug;

	protected BasicActionGetter caller;
	
	private boolean isPaused;
	
	protected Image pauseImage;
	
	protected Brain ia1;
	
	protected Brain ia2;
	
	protected int winCount1;
	
	protected int winCount2;
	
	protected int numberRounds = 2;
	
	protected FightTimer t;

	public Fight(BasicFighter fighter1, BasicFighter fighter2, Stage stage) {
		this(fighter1, fighter2, stage, DEFAULT_FIGHT_DURATION);
	}

	public Fight(BasicFighter fighter1, BasicFighter fighter2, Stage stage, 
			int fightDuration) {
		this.fighter1 = fighter1;
		this.fighter2 = fighter2;
		this.stage = stage;
		this.fightDuration = fightDuration;
		this.timer = fightDuration;
		this.health1 = new HealthBar(fighter1);
		this.health2 = new HealthBar(fighter2);
		this.ranking = new BasicFighter[2];
		this.ranking[0] = fighter1;
		this.ranking[1] = fighter2;
		this.isPaused = false;
		this.pauseImage = null;
		this.ia1 = null;
		this.ia2 = null;
		this.winCount1 = 0;
		this.winCount2 = 0;
		t = new FightTimer();
	}

	@Override
	public void init(GameContainer gc, StateBasedGame game) 
			throws SlickException {
		try {
			pauseImage = new Image(gc.getWidth(), gc.getHeight());
		} catch (SlickException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void enter(GameContainer gc, StateBasedGame game) 
			throws SlickException {
		this.buffer = new BasicBuffer<>();
		this.isDebug = false;
		this.debug = new DebugDrawer();
		this.debug.addElement(fighter1);
		this.debug.addElement(fighter2);
		int width = gc.getWidth();
		int height = gc.getHeight();
		this.stage = fighter1.getStage();
		this.stage.init(gc);
		this.collisions = new FighterCollisionManager();
		this.collisions.addElement(fighter1);
		this.collisions.addElement(fighter2);
		this.collisions.setGroundHeight(stage.getGroundHeight(gc));
		this.attackCollisions = new SimpleAttackCollisionManager();
		this.fighter1.getAttacks().setAttackCollision(this.attackCollisions);
		this.fighter2.getAttacks().setAttackCollision(this.attackCollisions);
		this.attackCollisions.addFighter(fighter1);
		this.attackCollisions.addFighter(fighter2);
		this.caller = BasicActionGetterInstance.getInstance();
		this.fighter1.init(gc);
		this.fighter2.init(gc);
		this.fighter1.setX(width / 10);
		this.fighter1.setY(height - stage.getGroundHeight(gc) - fighter1.getHeight());
		this.fighter2.setX(width - (width / 10) - fighter2.getWidth());
		this.fighter2.setY(height - stage.getGroundHeight(gc) - fighter2.getHeight());
		this.health1.init(gc);
		this.health2.init(gc);
		this.health1.setX((int) (width / 2 - 30 * this.health1.getScale() - health1.getWidth()));
		this.health1.setY(gc.getHeight() / 14);
		this.health2.setX((int) (width / 2 + 30 * this.health1.getScale()));
		this.health2.setY(gc.getHeight() / 14);
		this.fighter2.setForward(false);
		this.stage.playMusic();
		this.initTime = 0;
		this.ranking[0] = this.fighter1;
		this.ranking[1] = this.fighter2;
		if(ia1 != null) {
			ia1.setCollisions(this.attackCollisions);
		}
		if(ia2 != null) {
			ia2.setCollisions(this.attackCollisions);
		}
		if (winCount1 >= 2 || winCount2 >= 2) {
			winCount1 = winCount2 = 0;
		}
		this.fighter1.setBrain(ia1);
		this.fighter2.setBrain(ia2);
		t.setX(width / 2 - t.getWidth() / 2);
		t.setY((int) (50 * t.getScale()));
	}


	@Override
	public void render(GameContainer gc, StateBasedGame game, Graphics g)
			throws SlickException {
		String timerDisplay;
		this.stage.draw(g);
		this.fighter1.draw(g);
		this.fighter2.draw(g);
		g.setColor(Color.black);
		//g.fillRect(this.health1.getX(), this.health2.getY(), this.health1.getWidth()*2, this.health1.getHeight());
		this.health1.draw(g);
		this.health2.draw(g);
		this.t.draw(g);
		this.attackCollisions.draw(g);
		timerDisplay = timer < 10 ? "0" + timer : "" + timer;
		AngelCodeFont font = FontManager.getTimer();
		AngelCodeFont bigfont = FontManager.getBig();
		g.setFont(font);
		g.setColor(Color.white);
		g.drawString(timerDisplay, gc.getWidth() / 2 - font.getWidth(timerDisplay) / 2, this.health1.getY() + (this.health1.getHeight() * 0.75f) - font.getHeight(timerDisplay));
		if(this.isDebug) {
			this.debug.draw(g);
		}
		
		gc.setShowFPS(this.isDebug);
		String win = "";

		if(timer == 0 || fighter1.getHealthPoint() == 0 ||  fighter2.getHealthPoint() == 0) {
			if(ranking[0].compareTo(ranking[1]) == 0) {
				win = "DRAW GAME";
			} else {
				win = ranking[0].getFighterName().toUpperCase() + " WON!";
			}
		} else if (initTime <= TIME_START / 1.5) {
			win = "READY...";
		} else if (initTime <= TIME_START) {
			win = "FIGHT!";
		}
		
		g.setFont(bigfont);
		g.drawString(win, gc.getWidth() / 2 - bigfont.getWidth(win) / 2, gc.getHeight() * 4 / 9);
		if (this.isPaused) {
			pauseImage.draw(0, 0);
			g.drawString("PAUSE", gc.getWidth() / 2 - bigfont.getWidth("PAUSE") / 2, gc.getHeight() * 4 / 9);
		} else {
			g.copyArea(pauseImage, 0, 0);
		}
	}

	@Override
	public void update(GameContainer gc, StateBasedGame game, int ms)
			throws SlickException {
		if (this.isPaused) {
			return;
		}
		boolean forward;
		initTime += ms;
		this.timer = Math.max(0, (int)(fightDuration - initTime / 1000 + TIME_START / 1000));
		this.timer = Math.min(fightDuration, this.timer);
		
		if (initTime < TIME_START) {
			return;
		}
		
		this.fighter1.update(gc, ms);
		this.fighter2.update(gc, ms);
		this.stage.update(gc, ms);
		this.buffer.update(gc, ms);
		this.health1.update(gc, ms);
		this.health2.update(gc, ms);
		performAction(this.buffer.getTop(), true);
		
		forward = this.fighter1.getHitBox().getCenterX() < this.fighter2.getHitBox().getCenterX();
		if (this.fighter1.getHealthPoint() > 0) {
		    this.fighter1.setForward(forward);
		}
		if (this.fighter2.getHealthPoint() > 0) {
			this.fighter2.setForward(!forward);
		}
		
		this.attackCollisions.update(gc, ms);
		this.collisions.update(gc, ms);

		if(timer == 0 || fighter1.getHealthPoint() == 0 ||  fighter2.getHealthPoint() == 0) {
			Arrays.sort(this.ranking);
			stage.stopMusic();
			if (timer == 0) {
				if (ranking[0].compareTo(ranking[1]) == 0) {
					this.ranking[0].setHealthPoint(0);
				}
				this.ranking[1].setHealthPoint(0);
			}
		}
		if(ranking[1].isDead()) {
			if (ranking[0].compareTo(ranking[1]) == 0) {
				this.ranking[0].setHealthPoint(0);
			} else if(ranking[0] == fighter1) {
				winCount1++;
			} else {
				winCount2++;
			}
			if (winCount1 >= 2 || winCount2 >= 2) {
				game.enterState(Menu.ID, new FadeOutTransition(), new FadeInTransition());
			} else {
				game.enterState(ID, new FadeOutTransition(), new FadeInTransition());
			}		
		}
	}

	@Override
	public int getID() {
		return ID;
	}

	@Override
	public void controllerButtonPressed(int controller, int button) {
		manageInput(controller, "" + button, false);
	}

	@Override
	public void controllerButtonReleased(int controller, int button) {
		manageInput(controller, "" + button, true);
	}

	@Override
	public void controllerRightPressed(int controller) {
		manageInput(controller, "right", false);
	}

	@Override
	public void controllerRightReleased(int controller) {
		manageInput(controller, "right", true);
	}

	@Override
	public void controllerLeftPressed(int controller) {
		manageInput(controller, "left", false);
	}

	@Override
	public void controllerLeftReleased(int controller) {
		manageInput(controller, "left", true);
	}

	@Override
	public void controllerUpPressed(int controller) {
		manageInput(controller, "up", false);
	}

	@Override
	public void keyPressed(int key, char c) {
		String input = "" + c;
		System.out.println(input);
		if (key == Input.KEY_UP)	{
            input = "up";
		} else if (key == Input.KEY_LEFT)	{
			input = "left";
		} else if (key == Input.KEY_RIGHT)	{
			input = "right";
		} else if (key == Input.KEY_F1) {
			this.isDebug = !this.isDebug;
		}
		manageInput(-1, input, false);
	}

	@Override
	public void keyReleased(int key, char c) {
		String input = "" + c;
		if (key == Input.KEY_UP)	{
            input = "up";
		} else if (key == Input.KEY_LEFT)	{
			input = "left";
		} else if (key == Input.KEY_RIGHT)	{
			input = "right";
		}
		manageInput(-1, input, true);
	}

	public void manageInput(int controller, String button, boolean released) {
		Controls c = this.caller.getAction(controller, button);
		BasicFighter fighter;
		fighter = Controls.getPlayer(c) == 1 ? this.fighter1 : this.fighter2;
		if (isPaused && c != Controls.START_P1 && c != Controls.START_P2 
				|| !fighter.isControllable() || c == null || initTime < TIME_START) {
			return;
		}
		
		if (released) {
			performReleasedAction(c, fighter);
		} else {
			performAction(c, false);
		}	
	}
	
	protected void performAction(Controls c, boolean buffer) {
		if (c == null) {
			return;
		}
		BasicFighter fighter = Controls.getPlayer(c) == 1 ? this.fighter1 : this.fighter2;
		boolean act = false;
		switch(c) {
		case START_P1 : case START_P2 :
			this.isPaused = !this.isPaused;
			break;
		case RIGHT_P1 : case RIGHT_P2 :
			act = fighter.move(true);
			if (!act && !buffer) {
				this.buffer.buffer(c);
			}
			break;
		case LEFT_P1 : case LEFT_P2 :
			act = fighter.move(false);
			if (!act && !buffer) {
				this.buffer.buffer(c);
			}
			break;
		case UP_P1 : case UP_P2 :
			act = fighter.jump(10);
			if (!act && !buffer) {
				this.buffer.buffer(c);
			}
			break;
		case PARRY_P1 : case PARRY_P2 :
			act = fighter.setParrying(true);
			if (!act && !buffer) {
				this.buffer.buffer(c);
			}
			break;
		case Y_P1 : case Y_P2 : case A_P1 : case A_P2 : case TELEPORT_P2 :
		case X_P1 : case X_P2 : case B_P1 : case B_P2 : case TELEPORT_P1 :
			BasicAttack<?> attack = fighter.getAttacks().getAttack(c);
			act = fighter.attack(attack);
			if (!act && !buffer) {
				this.buffer.buffer(c);
			}
			if (buffer) {
				//attack.onRelease();
			}
			break;
		default :
			break;
		}
		if (buffer && act) {
			this.buffer.consume();
		}
	}
	
	protected void performReleasedAction(Controls c, BasicFighter fighter) {
		if (buffer.contains(c)) {
			buffer.remove(c);
		}
		switch(c) {
		case RIGHT_P1 : case RIGHT_P2 :
			if (fighter.getXspeed() > 0) {
				fighter.stop();
			}
			break;
		case LEFT_P1 : case LEFT_P2 :
			if (fighter.getXspeed() < 0) {
				fighter.stop();
			}
			break;
		case PARRY_P1 : case PARRY_P2 :
			fighter.setParrying(false);
			break;
		case Y_P1 : case Y_P2 : case A_P1 : case A_P2 :
		case X_P1 : case X_P2 : case B_P1 : case B_P2 :
			BasicAttack<?> attack = fighter.getAttacks().getAttack(c);
			if (attack == fighter.getCurrentAttack()) {
				attack.onRelease();
			}
			break;
		default :
			break;
		}
	}

	public BasicFighter getFighter1() {
		return fighter1;
	}

	public void setFighter1(BasicFighter fighter1) {
		this.fighter1 = fighter1;
		this.health1 = new HealthBar(fighter1);
		if(ia1 != null) {
			ia1.setCollisions(this.attackCollisions);
		}
	}

	public BasicFighter getFighter2() {
		return fighter2;
	}

	public void setFighter2(BasicFighter fighter2) {
		this.fighter2 = fighter2;
		this.health2 = new HealthBar(fighter2);
		if(ia2 != null) {
			ia2.setCollisions(this.attackCollisions);
		}
	}

	public Brain getIa1() {
		return ia1;
	}

	public void setIa1(Brain ia1) {
		this.ia1 = ia1;
	}

	public Brain getIa2() {
		return ia2;
	}

	public void setIa2(Brain ia2) {
		this.ia2 = ia2;
	}	
}
